import { EventsRes, RawMeasureResult, DocumentObject } from '@amcax/base';
import { Renderer3D, RendererAssembly, RendererMgr } from '@amcax/renderer';
import { parseMeasureResult } from '@app-base/common/utils/measure';
import { MeasureChildObject } from '@app-base/common/utils/measure/ChildObject';
import { MeasureEvents } from '@app-base/common/utils/measure/events';
import { MeasureLabel } from '@app-base/common/utils/measure/Label';
import { MeasureObject } from '@app-base/common/utils/measure/renderObject';
import { MeasureLabelPositions } from '@app-base/common/utils/measure/types';
import { ctx2, docMgr, userEvents } from '@app-cad/module';
import { isEqual } from 'lodash';
import { Object3D } from 'three-legacy';
import { measurementRootNode } from '../pages/product-support/assembly-funcs';

class MeasureRenderManager {
  measureObjects: Map<string, MeasureObject> = new Map();
  measureResults: Map<string, RawMeasureResult> = new Map();
  // 标签位置缓存
  labelPositions: Map<string, MeasureLabelPositions> = new Map();

  private isProduct: boolean;
  private renderer: Renderer3D | RendererAssembly;
  private group: Object3D;
  private readonly groupName = 'measureGroup';

  init(isProduct = false) {
    this.isProduct = isProduct;

    this.renderer = isProduct
      ? RendererMgr.getRender<RendererAssembly>('assembly')
      : RendererMgr.getRender<Renderer3D>('3d');

    const existGroup = ctx2.cadScene.workGroup.children.find(
      (o) => o.name === this.groupName,
    );

    if (existGroup != null) {
      this.group = existGroup;
    } else {
      this.group = new Object3D();
      this.group.name = this.groupName;
      ctx2.cadScene.workGroup.add(this.group);
    }
  }

  private getMeasureResults() {
    if (this.isProduct) {
      return (measurementRootNode?.children?.map?.(
        (child) => child.originalObj,
      ) || []) as DocumentObject[];
    } else {
      return docMgr.measureResult;
    }
  }

  // 渲染测量结果
  async rerenderMeasureResults() {
    Array.from(this.measureResults.keys()).forEach((key) => {
      if (!this.getMeasureResults().some((r) => r.dxid === key)) {
        this.removeMeasureResult([key], true);
      }
    });
    await Promise.all(
      this.getMeasureResults()?.map(async (m) => {
        const dxid = m.dxid;
        if (isEqual(m.params, this.measureResults.get(dxid))) {
          return;
        }
        this.removeMeasureResult([m.dxid], true);
        if (!m.visibility) {
          return;
        }
        this.measureResults.set(dxid, m.params);
        const newRenderObj = new MeasureObject(
          parseMeasureResult(m.params, m.dxid, this.labelPositions.get(m.dxid)),
        );
        await newRenderObj.render();
        this.measureObjects.set(dxid, newRenderObj);
        this.group.add(newRenderObj);
      }),
    );
    ctx2.viewer.requestRender();
  }

  removeMeasureResult(ids: string[], onlyRender = false) {
    if (this.renderer == null) {
      return;
    }

    ids.forEach((id) => {
      const target = this.measureObjects.get(id);
      target?.destroy();
      this.measureObjects.delete(id);
      if (!onlyRender) {
        this.measureResults.delete(id);
        this.labelPositions.delete(id);
      }
    });

    ctx2.viewer.requestRender();
  }

  setVisibility(id: string, visibility: boolean) {
    if (this.renderer == null) {
      return;
    }

    const target = this.measureResults.get(id);
    if (target) {
      target.visibility = visibility;
    }

    const renderTarget = this.measureObjects.get(id);
    if (!visibility && renderTarget != null) {
      this.labelPositions.set(id, renderTarget.getLabelPositions());
    }

    this.rerenderMeasureResults();
  }

  hoveredObject: MeasureObject;
  selectedObject: MeasureObject;

  isDraggingLabel = false;

  pick(ev: EventsRes<MouseEvent>) {
    const newHoveredObject = this.getObjectByEvent(ev);
    if (newHoveredObject !== this.hoveredObject) {
      this.hoveredObject?.setHover(false);
      this.hoveredObject = newHoveredObject;
      this.hoveredObject?.setHover(true);
      ctx2.viewer.requestRender();
    }
    setTimeout(() => {
      ctx2.viewer.requestRender();
    });
    return this.hoveredObject;
  }

  selectByButton({ onRight }: { onRight: boolean }) {
    if (this.isDraggingLabel) {
      return this.selectedObject;
    }

    const newSelectedObject = this.hoveredObject;
    if (newSelectedObject !== this.selectedObject) {
      this.selectedObject?.setSelect(false);
      this.selectedObject = newSelectedObject;
      this.selectedObject?.setSelect(true);
    }
    setTimeout(() => {
      ctx2.viewer.requestRender();
    });

    if ((onRight && this.selectedObject?.measureResult?.id) || !onRight) {
      userEvents.emit(
        MeasureEvents.SelectFromCanvas,
        this.selectedObject?.measureResult?.id,
      );
    }

    return this.selectedObject;
  }

  selectById(id: string) {
    const newSelectedObject = this.measureObjects.get(id);
    if (newSelectedObject !== this.selectedObject) {
      this.selectedObject?.setSelect(false);
      this.selectedObject = newSelectedObject;
      this.selectedObject?.setSelect(true);
    }
    setTimeout(() => {
      ctx2.viewer.requestRender();
    });

    return this.selectedObject;
  }

  private getObjectByEvent(ev: EventsRes<MouseEvent>) {
    // 先判断是不是hover到了html元素
    const target = ev.event.target as HTMLElement;

    // MeasureLabel的dom最多两层就是root
    const nodes = [
      target,
      target.parentElement,
      target.parentElement?.parentElement,
    ].filter(Boolean);

    const measureLabelRootNode = nodes.find((n) =>
      n.id.startsWith(MeasureLabel.domIDPrefix),
    );

    if (measureLabelRootNode != null) {
      const result = Array.from(this.measureObjects.values()).find(
        (o) =>
          o.uuid ===
          measureLabelRootNode.id.replace(MeasureLabel.domIDPrefix, ''),
      );

      if (result != null) {
        return result;
      }
    }

    const result = ctx2.viewer.sceneSetup.raycast(
      ev.mouse,
      Array.from(this.measureObjects.values()).filter((v) => v.visible),
    )[0];

    if (result?.object instanceof MeasureObject) {
      return result?.object;
    }

    return (
      (result?.object as MeasureChildObject)?.getRootObject?.() ??
      (result?.object?.parent as MeasureChildObject)?.getRootObject?.()
    );
  }
}

export const measureRenderManager = new MeasureRenderManager();
